• 对于 Spring 框架而言,一切 Java 对象都是 Bean
  • Spring 的 IoC 容器降低了业务对象替换的复杂性,提高了组件之间的解耦
  • Spring 的 AOP 支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用
  • Spring 的 ORM 和 DAO 提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问
  • 注意:Spring 的核心容器必须依赖于 common-logging.jar
  • https://docs.spring.io/spring/docs/current/spring-framework-reference/index.html

spring-overview

# Spring 整合 JUnit 测试

// 让 JUnit 直接启动 Spring 容器,测试类运行在容器中
@RunWith(SpringJUnit4ClassRunner.class)
// 启动 Spring 容器时去哪加载配置文件,默认加载当前包下的 "当前类名-context.xml"
@ContextConfiguration("classpath:applicationContext.xml")
// 声明为集成测试加载的 ApplicationContext 是 WebApplicationContext
// @WebAppConfiguration
1
2
3
4
5
6

# Spring 容器

// AbstractApplicationContext#refresh

// 初始化 BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 调用容器后处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 Bean 后处理器
registerBeanPostProcessors(beanFactory);
// 初始化信息源
initMessageSource();
// 初始化应用上下文事件广播器
initApplicationEventMulticaster();
// 初始化其它特殊的 Bean:由具体子类实现
onRefresh();
// 注册事件监听器
registerListeners();
// 初始化所有单例的 Bean,使用懒加载模式的 Bean 除外
// 1. Trigger initialization of all non-lazy singleton beans
// 2. Trigger post-initialization callback for all applicable beans
finishBeanFactoryInitialization(beanFactory);
// 完成刷新并发布容器刷新事件
finishRefresh();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 接口层描述了容器的重要组件及组件间的协作关系
  • 继承体系逐步实现组件的各项功能

# Resource 接口(资源访问)

常见的前缀及对应的访问策略:

  1. classpath: ——使用 ClassPathResource 从类路径中加载资源。 classpath:classpath:/ 是等价的,都是相对于类的根路径。classpath: 只会在第一个加载的包路径下查找,而 classpath* 会扫描所有这些 JAR 包及路径下出现的相同类路径。资源文件可以在标准的文件系统中,也可以在 JAR 或 ZIP 的类包中
  2. file: ——使用 FileSystemResource 从本地文件系统加载资源,以斜杠开头表示绝对路径,否则表示相对路径
  3. http://ftp:// ——使用 UrlResource 访问基于 HTTP 或 FTP 协议的网络资源
  4. (无前缀)——由 ApplicationContext 的实现类来决定访问策略

用 Resource 操作文件时,如果不是本地文件系统中存在的文件(如 JAR 包中的文件、网络文件),不能使用 getFile() 方法,而应使用 getInputStream() 方法

// ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// Resource resource = resolver.getResource("classpath:applicationContext.xml");
Resource resource = new ClassPathResource("applicationContext.xml");

ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources("classpath*:/mapper/**/*.xml");
1
2
3
4
5
6

# Spring 容器的创建方式

# BeanFactory 接口

  • 负责配置、创建、管理 Bean
  • 在初始化容器时,并未实例化 Bean,直到第一次访问某个 Bean 时才实例化目标 Bean
  • 常见实现类:DefaultListableBeanFactory(DefaultListableBeanFactory#preInstantiateSingletons)、ConfigurationClassPostProcessor、XmlBeanFactory
  • 常用方法
    • boolean containsBean(String name):判断 Spring 容器是否包含 id 为 name 的 Bean 实例
    • T getBean(Class<T> requiredType):获取 Spring 容器中属于 requiredType 类型的、唯一的 Bean 实例
    • Object getBean(String name):返回容器 id 为 name 的 Bean 实例
    • T getBean(String name, Class requiredType):返回容器中 id 为 name,并且类型为 requiredType 的 Bean
    • Class<?> getType(String name):返回容器中 id 为 name 的 Bean 实例的类型
Resource resource = new ClassPathResource("applicationContext.xml");
// BeanFactory factory = new XmlBeanFactory(resource);
DefaultListableBeanFactory factory =  new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
1
2
3
4
5

# ApplicationContext 接口

  • Spring 上下文,BeanFactory 的子接口
  • 常用实现类:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext 和 AnnotationConfigApplicationContext
  • 在初始化应用上下文时就实例化所有单实例的 Bean
  • 系统前期创建 ApplicationContext 时将有较大的系统开销,但一旦 ApplicationContext 初始化完成,程序后面获取 singleton Bean 实例时将拥有较好的性能
// 使用 XML 配置文件提供 Bean 定义信息启动容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 使用 Java 配置类提供 Bean 定义信息启动容器
// ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class); // 注册配置类
ctx.refresh(); // 刷新容器以应用这些注册的配置类
1
2
3
4
5
6
7
  • AbstractApplicationContext 抽象类中的方法:
    Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType):获取所有带有指定注解的 Beans 集合
    T getBean(String name, Class<T> requiredType):根据容器中 Bean 的 id 来获取指定 Bean

# WebApplicationContext 接口

  • Spring Web 上下文,ApplicationContext 的子接口

mvc-context-hierarchy

# 父子容器

  • 通过 HierarchicalBeanFactory 接口,Spring 的 IoC 容器可以建立父子层级关联的容器体系
  • 子容器可以访问父容器中的 Bean,但父容器不能访问子容器中的 Bean
  • 在容器内,Bean 的 id 必须是唯一的,但子容器可以拥有一个和父容器 id 相同的 Beam

# Aware 容器感知

  • BeanFactoryAware、ApplicationContextAware:获取 Spring 容器
    • 自定义 Bean 实现 BeanFactoryAware 接口,重写该接口中的 setBeanFactory(BeanFactory beanFactory) 方法,当 Spring 调用该方法时会将 Spring 容器作为参数传入该方法
    • 自定义 Bean 实现 ApplicationContextAware 接口,重写该接口中的 setApplicationContext(ApplicationContext applicationContext) 方法,当 Spring 容器调用该方法时会把自身作为参数传入该方法
  • EnvironmentAware:获取环境 Environment
  • BeanNameAware:获取当前 Bean 的名称
  • ResourceLoaderAware:获取资源加载器 ResourceLoader
  • ServletContextAware:获取 ServletContext
  • ImportAware:获取到被 @Import 导入的配置类的 AnnotationMetadata
  • ApplicationEventPublisher:获取事件发布器

# ApplicationContextInitializer

  • 回调接口,用于在 ConfigurableApplicationContext 类型(或其子类型)的容器进行刷新 refresh 之前,初始化 ConfigurableApplicationContext 实例

# BeanDefinitionRegistry

  • BeanDefinitionRegistry 接口提供了向容器手动注册 BeanDefinition 对象的方法

# Spring Environment

  • Environment 是一种在容器内以 Profile 和 Property 为模型的应用环境抽象整合:
    • 针对 Property,又抽象出各种 PropertySource 类代表配置源。一个环境下可能有多个配置源,每个配置源中有诸多配置项。在查询配置信息时,需要按照配置源优先级进行查询。常见的配置源 ConfigurationPropertySourcesPropertySource、PropertiesPropertySource(JVM 系统配置)、OriginAwareSystemEnvironmentPropertySource(配置文件配置) 等。
    • Profile 定义了场景的概念。通常,我们会定义类似 dev、test 和 prod 等环境作为不同的 Profile,用于按照场景对 Bean 进行逻辑归属。同时,Profile 和配置文件也有关系,每个环境都有独立的配置文件,但我们只会激活某一个环境来生效特定环境的配置文件。
  • Spring Framework 提供了两种 Environment 的实现(ConfigurableEnvironment 子接口的实现类)
    1. StandardEnvironment(一般应用),包含的属性源名称:systemProperties(JVM 系统属性)、systemEnvironment(操作系统环境变量)
    2. StandardServletEnvironment(Web 应用),包含的属性源名称: servletContextInitParams、servletConfigInitParams、jndiProperties

Spring容器相关类的继承体系

# applicationContext.xml

  • <import resource="classpath:其它 xml 配置文件"/>,引入其它分支配置文件

# IoC 和 DI

  • IoC:Inverse of Control(控制反转):将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,即由 Spring 容器负责创建 Bean(即 Java 对象)

  • DI:Dependency Injection(依赖注入):Spring 容器管理容器中 Bean 之间依赖关系的方法

# 后置处理器

  • Spring 提供了两种常用的后置处理器:BeanPostProcessor、BeanFactoryPostProcessor

# BeanPostProcessor(Bean 后置处理器)

  • 对容器中所有 Bean 进行后处理,对 Bean 进行额外加强
  • 方法:
    • postProcessBeforeInitialization(初始化前执行)
    • postProcessAfterInitialization(初始化后执行)
  • 例如:为容器中的目标 Bean 生成代理(AnnotationAwareAspectJAutoProxyCreator)、增加对 IoC 和 DI 注解的支持(如 AutowiredAnnotationBeanPostProcessor 用于处理 @Autowired 和 @Value)等
  • 子接口 InstantiationAwareBeanPostProcessor
    • 方法:
      • postProcessBeforeInstantiation(实例化前执行)
      • postProcessAfterInstantiation(实例化后执行)
      • postProcessPropertyValues(属性注入)
    • 子接口 SmartInstantiationAwareBeanPostProcessor

BeanPostProcessor的五大接口

  1. BeanPostProcessor Bean 后置处理器(对象初始化前后的回调)
    • postProcessBeforeInitialization:实例化、依赖注入后,在调用显式的初始化(init-method、InitializingBean 等)之前执行。如:
      • BeanValidationPostProcessor 完成 JSR-303 @Valid 注解 Bean 的验证
      • InitDestroyAnnotationBeanPostProcessor 完成 @PostConstruct 注解的初始化方法调用
      • ApplicationContextAwareProcessor 完成一些 Aware 接口的注入(如 EnvironmentAware、ResourceLoaderAware、ApplicationContextAware)
    • postProcessAfterInitialization:在调用显式的初始化之后执行,如
      • AspectJAwareAdvisorAutoProxyCreator 完成 xml 风格的 AOP 配置(aop:config)的目标对象包装到 AOP 代理对象
      • AnnotationAwareAspectJAutoProxyCreator:完成 @Aspectj 注解风格(aop:aspectj-autoproxy)的 AOP 配置的目标对象包装到 AOP 代理对象
      • AsyncAnnotationBeanPostProcessor:完成 @Async 标注的目标对象包装到 AOP 代理对象,对应的切面类AnnotationAsyncExecutionInterceptor
      • MethodValidationPostProcessor:完成 @Validated 标注的目标对象包装到 AOP 代理对象,用于支持对方法级别数据(方法参数/方法返回值)进行验证,对应的切面类 MethodValidationInterceptor
  2. InstantiationAwareBeanPostProcessor 实例化 Bean(对象实例化前后以及实例化后设置 propertyValues 的回调)
    • postProcessBeforeInstantiation:实例化之前执行,可以用来在对象实例化前直接返回一个对象(如代理对象)来代替通过内置的实例化流程创建对象
    • postProcessAfterInitialization:实例化完毕后、属性注入前 populateBean() 执行。若方法返回 false,后续的 postProcessPropertyValues 不再执行,Spring 也不再对对应的 Bean 实例进行自动依赖注入
    • postProcessPropertyValues:紧接着上面 postProcessAfterInitialization 执行。如:
      • AutowiredAnnotationBeanPostProcessor 执行 @Autowired 注解注入
      • CommonAnnotationBeanPostProcessor 执行 @Resource 等注解的注入
      • PersistenceAnnotationBeanPostProcessor 执行 @PersistenceContext 等 JPA 注解的注入
      • RequiredAnnotationBeanPostProcessor 执行 @Required 注解的检查等
  3. SmartInstantiationAwareBeanPostProcessor 智能实例化 Bean
    • 抽象子类:InstantiationAwareBeanPostProcessorAdapter、AbstractAutoProxyCreator
    • predictBeanType:预测 Bean 的类型
    • determineCandidateConstructors:获取 Bean 的候选构造器,若返回 null,则使用空构造函数去实例化。如:
      • AutowiredAnnotationBeanPostProcessor:扫描 Bean 中使用了 @Autowired 或 @Value 注解的构造器从而完成构造器注入
    • getEarlyBeanReference:获取要提前暴露的 Bean 的引用,用来支持单例对象的循环引用
      • AspectJAwareAdvisorAutoProxyCreator 或 AnnotationAwareAspectJAutoProxyCreator

当需要实现 SmartInstantiationAwareBeanPostProcessor 或者 InstantiationAwareBeanPostProcessor 中的某个方法,可以通过继承 InstantiationAwareBeanPostProcessorAdapter(做了所有方法的空实现)

  1. MergedBeanDefinitionPostProcessor 合并 Bean 定义

    • postProcessMergedBeanDefinition:执行 Bean 定义的合并
  2. DestructionAwareBeanPostProcessor 销毁 Bean

    • postProcessBeforeDestruction:销毁后处理回调方法,该回调只应用于单例 Bean。如:
      • InitDestroyAnnotationBeanPostProcessor 完成 @PreDestroy 注解的销毁方法调用

# BeanFactoryPostProcessor(容器后置处理器)

  • 对 IoC 容器进行后处理,用于增强容器功能,如动态添加 Bean(DefaultListableBeanFactory#registerBeanDefinition、DefaultListableBeanFactory#registerSingleton)
  • 在应用上下文装配配置文件后立即调用
  • 方法:void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
  • 例如:PropertyPlaceholderConfigurer(属性占位符配置器)、ConfigurationClassPostProcessor(@Configuration 配置文件的解析)

# FactoryBean<T> 接口

  • 可以通过实现该工厂类接口定制实例化 Bean 的逻辑

  • 接口方法:

    • T getObject():返回由 FactoryBean 创建的 Bean 实例,如果 isSingleton() 返回 true,则该实例会放到 Spring 容器的单实例缓存池中
    • boolean isSingleton():确定由 FactoryBean 创建的 Bean 的作用域是 singleton 还是 prototype
    • Class<?> getObjectType():返回 FactoryBean 创建 Bean 的类型
  • 常见实现类:MapperFactoryBean<T>

# ObjectFactory<T> 接口

  • 对象工厂,其实现通常被定义为作为 API(通过注入)提供给其它 Bean
  • 用途:
    1. 从对应的域中获取到指定名称的对象:Scope 接口中的 Object get(String name, ObjectFactory<?> objectFactory);
    2. 允许注入点能够被延迟注入:ConfigurableListableBeanFactory 接口中void registerResolvableDependency(Class<?> dependencyType, Object autowiredValue);
  • 接口方法:T getObject()
  • 常见实现类:RequestObjectFactory、ResponseObjectFactory、SessionObjectFactory、WebRequestObjectFactory

# ObjectProvider<T> 接口

  • ObjectFactory<T> 的子接口,ObjectFactory<T> 的一种变体

  • 用途(DefaultListableBeanFactory#resolveDependency):使用构造函数注入时,如果注入实例为空时,使用 ObjectProvider 可避免了强依赖导致的依赖对象不存在异常;如果有多个实例,ObjectProvider 的方法可以根据 Bean 实现的 Ordered 接口或 @Order 注解指定的先后顺序获取一个 Bean

    @Component
    public class IndexService {
        private A a;
        private B b;
    
        public IndexService(ObjectProvider<A> a, ObjectProvider<B> b) {
            this.b = b.getIfAvailable();
            this.b = b.orderedStream().findFirst().orElse(null);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# 使用 XML 文件配置 IoC

  • <bean>,定义一个 Bean,需指定的属性

    1. 调用构造器:id、class(该 Bean 的实现类)、parent(所继承 Bean 的 id)

    2. 调用静态工厂方法:id、class(静态工厂类)、factory-method

    3. 调用实例工厂方法:id、factory-bean(实例工厂 Bean 的 id)、factory-method

    4. 调用实例工厂方法:id、class(实现了 FactoryBean<T> 的实例工厂类型

      配置 Bean 时,可以不设置 id,也可以不设置 name,Spring 默认会使用类的全限定名作为 Bean 的标识符
      如果使用 id 属性来设置 Bean 的标识符,那么 id 在 Spring 容器中必需唯一
      如果使用 name 属性来设置,那么设置的其实就是 Bean 的标识符,必需在容器中唯一
      如果同时设置 id 和 name,那么 id 设置的是标识符(identifier),name 设置的是别名(aliases)
      name 属性设置多个值(多个 name 用 , ; 或 空格分割),当不设置 id 时,name 属性值的第一个被用作标识符,其它的被视为别名;如果设置了 id,那么 name 的所有值都是别名

      当配置文件中 <bean> 的 class 属性配置的实现类是 FactoryBean 时,通过 BeanFactory#getBean() 方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法所返回的对象,相当于 FactoryBean#getObject() 代理了 BeanFactory#getBean() 方法
      如果需要获取 FactoryBean 实例,则需要在使用 getBean(beanName) 方法时显式地在 beanName 前加上 '&' 前缀

  • 其它属性:

    • scope:定义 Bean 的作用域,属性值:singleton(缺省)、prototype(每次获取都新建一个 Bean 的实例)、session、request
    • init-method:初始化后执行的方法
    • destroy-method:销毁前执行的方法(只适用于 singleton Bean)
    • abstract="true":定义成抽象 Bean
    • lazy-init="true”:阻止 Spring 容器预初始化容器中的 singleton Bean

# 使用 XML 文件配置 DI

# 设值注入(属性注入)

需提供对应的 setter 方法及存在无参构造器
属性变量名前两个字母要么全部大写,要么全部小写

<property> 子标签

  • 属性:name、value(普通的属性值或属性占位符)、ref(所引用 Bean 的 id)

  • 子标签:

    • <bean>,嵌套 Bean,容器不能获取,无须指定 id 属性
    • <list>,每个子标签 <value>、<ref> 或 <bean> 配置一个 List 元素
    • <set>,每个子标签 <value>、<ref> 或 <bean> 配置一个 Set 元素
    • <array>,每个子标签 <value>、<ref> 或 <bean> 配置一个数组元素
    • <map>,子标签 <entry>,属性:key、key-ref、value、value-ref
    • <props>,子标签 <prop>,属性 key 指定 key 的值,内容指定 value 的值
    • <value>,"key"="value"
  • value 的属性值可以使用属性占位符

    • PropertyPlaceholderConfigurer 是一个容器后处理器,负责加载 Properties 属性文件里的属性值,并将这些属性值设置成 Spring 配置文件的数据
    <!-- 引入 属性占位符 -->
    <context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/>
    <!-- 定义数据源 Bean,使用 Druid 数据源实现 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">
        <property name="driverClassName" value="${driverClassName}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# 构造函数注入

需提供对应的、带参数的构造器
构造函数入参引用的对象必须已经准备就绪,所以如果两个 Bean 都采用构造函数注入,而且都通过构造函数入参引用对方,这种类型导致的循环依赖问题 Spring 无法解决

  • <constructor-arg> 子标签,代表一个构造器参数,属性:name、value、index、type、ref

# 使用注解配置 IoC 和 DI

  • Spring 通过使用 Bean 后处理器(BeanPostProcessor)增加对注解的支持

  • 启动包扫描功能,以注册在指定包下带有 IoC 和 DI 相关注解的 Bean:<context:component-scan base-package="包1,包2, ..."/>

  • 启用 4 个 Bean 后处理器(当启动了包扫描功能后,在 Spring 测试环境中可以省略):<context:annotation-config />

# IoC 相关注解

  • 标注 Bean 类,指定该 Java 类作为 Spring Bean,Bean 实例的名称默认是 Bean 类的首字母小写,其它部分不变
    @Component("bean 的 id") // 缺省的 Bean 名称为首字母小写的 Bean 类名(AnnotationBeanNameGenerator,但当第一个和第二个字符都是大写字符时保留不变
    @Repository:在数据访问层(dao 层)使用
    @Service:在业务逻辑层(service 层)使用
    @Controller:在展现层(MVC→Spring MVC)使用
    @Primary:@Autowired 自动装配找到多个匹配的 Bean时,首选该 Bean
  • 指定 Bean 的作用域、代理方式,如 @Scope(value = "prototype", proxyMode = ScopedProxyMode.DEFAULT)
  • 指定 Bean 的加载顺序,如 @Order(1)值越大优先级反而越低
  • 标注方法
    @PostConstruct:指定 Bean 的初始化方法
    @PreDestroy:指定 Bean 销毁之前的方法
  • DefaultListableBeanFactory#allowBeanDefinitionOverriding,是否允许重新注册具有相同名称的不同 Bean 定义,默认 true

当为单例的 Bean 注入 prototype 的 Bean 时,由于单例的 Bean 注入的依赖 Bean 是一次性创建的,所以即使依赖 Bean 本身设置的 Scope 属性为 prototype,也不会生效@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE),修复方式:

  1. 让依赖 Bean 以代理方式注入,@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
  2. 修改 Scope 属性为 prototype,且每次直接从 ApplicationContext 中获取依赖 Bean

# DI 相关注解(配置依赖)

  • @Autowired 自动装配

    • 可修饰实例变量、setter 方法、普通方法(方法的参数可以有多个)、构造器参数等
    • 自动搜索容器中类型匹配的 Bean 实例
    • 匹配顺序(by type 自动装配策略):先根据类型找到对应的 Bean,如果对应类型的 Bean 不是唯一的,再根据其属性名称和 Bean 的名称进行匹配
    • 若找不到或找到多个相同类型、相同名称的 Bean 则抛出异常,可设置 @Autowired(required=false)
    • 消除歧义性——@Primary 和 @Qualifier
    • 也可用在数组、List、Set 等数据结构上,目标 Bean 可通过实现 org.springframework.core.Ordered 接口或使用 @Order 注解指定其在数组、List 中的顺序(默认为注册顺序)
    • 也可用在 Map 上(Map 的 key 必须为 String 类型,此时 key 是 Bean 的名字)
  • @Qualifier("指定 Bean 的 id") 精确装配

    • 可修饰实例变量、方法的形参
    • 若找不到则不注入
  • @Resource(name="指定 Bean 的 id")

    • JavaEE 的注解
    • 可修饰实例变量或 setter 方法
    • 默认匹配顺序:属性的名称、属性的类型(如果 name 属性一旦指定,就只会按照名称进行装配
  • @Value:属性占位符需要放到 ${key:defaultValue} 之中,SpEL 表达式要放到 #{ ... } 之中

    @Value("#{T(System).currentTimeMillis()}")
    private Long initTime;
    // 赋值字符串
    @Value("#{'使用 Spring EL 赋值字符串'}")
    private String str;
    // 科学计数法赋值
    @Value("#{9.3E3}")
    private double d;
    // 赋值浮点数
    @Value("#{3.14}")
    private float pi;
    // 这里的 beanName 是 Spring IoC 容器 Bean 的名称,str 是其属性,? 含义是判断这个属性是否为空,如果不为空才去执行 toUpperCase 方法
    @Value("#{beanName.str?.toUpperCase()}")
    private String otherBeanProp;
    // 数学运算
    @Value("#{1+2}")
    private int run;
    // #浮点数比较运算
    @Value("#{beanName.pi == 3.14f}")
    private boolean piFlag;
    // 字符串比较运算
    @Value("#{beanName.str eq 'Spring Boot'}")
    private boolean strFlag;
    // 字符串连接
    @Value("#{beanName.str + ' 连接字符串'}")
    private String strApp;
    // 三元运算
    @Value("#{beanName.d > 1000 ? '大于' : '小于'}")
    private String resultDesc;
    @#{person.age?:20}
    private int age;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

Bean不同配置方式比较

# Bean 的生命周期

ApplicationContext中Bean的生命周期

  • DefaultSingletonBeanRegistry 中的缓存:

    1. singletonObjects:一级缓存,缓存完全初始化好的单例 Bean
    2. earlySingletonObjects:二级缓存,缓存提前暴露的单例 Bean(即还未完成属性注入的 Bean)
    3. singletonFactories:三级缓存,缓存需要提前暴露对象的对象工厂(用于解决循环依赖)
    4. singletonsCurrentlyInCreation:缓存正在创建中的 Bean 的名称
  • AbstractBeanFactory 中的缓存:

    • alreadyCreated:缓存至少已创建一次的 Bean 的名称
  • DefaultListableBeanFactory#preInstantiateSingletons

  • AbstractBeanFactory#getBean

  • AbstractBeanFactory#doGetBean

    • 调用 DefaultSingletonBeanRegistry#getSingleton(beanName, true),此时允许引用提前暴露的 Bean(解决循环引用):先从 singletonObjects 中获取 Bean,不存在或对象正在创建中,再从 earlySingletonObjects 中获取 Bean,不存在,从 singletonFactories 中获取 Bean,存在,添加到 earlySingletonObjects,并从 singletonFactories 中移除(即从三级缓存移动到二级缓存)
    • 不存在,调用 DefaultSingletonBeanRegistry#getSingleton(beanName, singletonFactory),singletonFactory.getObject() 创建后再添加到 singletonObjects。singletonFactory.getObject() 实际上是调用 AbstractAutowireCapableBeanFactory#createBean --> Spring 创建 Bean 过程可分为三步(AbstractAutowireCapableBeanFactory#doCreateBean):
      1. 实例化 AbstractAutowireCapableBeanFactory#createBeanInstance,实例化后使用该 Bean 创建对象工厂并添加到 singletonFactories,并从 earlySingletonObjects 中移除,如果该 Bean 需要被代理,bp instanceof SmartInstantiationAwareBeanPostProcessor,此时添加的是 AbstractAutoProxyCreator#getEarlyBeanReference 返回的代理对象(即提前创建代理)
      2. 属性注入 AbstractAutowireCapableBeanFactory#populateBean
      3. 初始化 AbstractAutowireCapableBeanFactory#initializeBean

IoC流水线

  • Spring 启动时读取应用程序提供的 Bean 配置信息(XML、注解、Java Config、Groovy DSL),并在 Spring 容器中生成一份相应的 Bean 配置注册表 BeanDefinitionRegistry,然后根据这张注册表实例化 Bean,装配好 Bean 之间的依赖关系,为上层应用提供准备就绪的运行环境

# SpEL

ExpressionParser parser = new SpelExpressionParser(); // 创建表达式解析器(可重用、线程安全)
// Expression expression = parser.parseExpression("'Hello World'.concat('!')"); // 对表达式字符串进行解析
// String message = expression.getValue(String.class); // Hello World!

String expressionStr = "Hello, #{#user} --> #{T(System).getProperty('user.home')}"; // 变量,使用 # + 变量名引用,默认对 java.lang 包可见
Expression expression = parser.parseExpression(expressionStr, ParserContext.TEMPLATE_EXPRESSION); // 对模版表达式字符串进行解析
StandardEvaluationContext context = new StandardEvaluationContext(); // 如果需要解析引用,在计算时设置一个表达式上下文对象
context.setVariable("user", "Aspire"); // 在上下文中设定变量
String message = expression.getValue(context, String.class); // Hello, Aspire --> C:\Users\Aspire
1
2
3
4
5
6
7
8
9

# Spring 的事件

  • ApplicationContext 的事件机制:当 ApplicationContext 发布 ApplicationEvent 时,容器主控程序将调用事件广播器 ApplicationEventMulticaster (AbstractApplicationContext#applicationEventMulticaster)将事件通知给事件监听器注册表中的 ApplicationListener,ApplicationListener 分别对事件进行响应
  • 在 Spring 中的事件,默认是同步处理的(SimpleApplicationEventMulticaster#taskExecutor 为 null)
  1. 自定义事件,继承 ApplicationEvent,并在其构造函数中指定事件源 source 以及事件关联的对象
    ApplicationEvent 两个子类:ApplicationContextEvent(容器事件,拥有 4 个子类,分别表示容器启动 ContextStartedEvent、刷新 ContextRefreshedEvent、停止 ContextStoppedEvent 及关闭 ContextClosedEvent 的事件)、RequestHandledEvent(与 Web 应用相关的事件,当一个 HTTP 请求被处理后, 产生该事件)

  2. 定义事件监听器

    方式 1:实现 ApplicationListener<E extends ApplicationEvent> 接口,并指定监听的事件类型,重写该接口中的 void onApplicationEvent(E event) 方法(判断该事件对象是否是想要监听的事件,并对消息进行接收处理)

    方式 2:在处理事件的 Bean 的方法上添加 @EventListener,并在该方法参数上指定要监听的事件,或者用 value 属性指定

  3. 使用容器发布事件

    方式 1:注入 ApplicationContext,使用 ApplicationContext.publishEvent() 方法来发布事件

    方式 2:实现 ApplicationEventPublisherAware 接口,使用 ApplicationEventPublisher.publishEvent() 方法来发布事件

  • Spring Boot 中的事件(org.springframework.boot.context.event.SpringApplicationEvent 子类):
    ApplicationStartingEvent(开始启动中)、ApplicationEnvironmentPreparedEvent(环境已准备好)、ApplicationContextInitializedEvent(上下文已实例化)、ApplicationPreparedEvent(上下文已准备好)、ApplicationStartedEvent(应用已启动)、ApplicationReadyEvent(应用已准备好)、ApplicationFailedEvent

# AOP 思想

  • AOP(Aspect Orient Programming),面向切面编程,将程序运行过程分解成各个切面
  • AOP 的作用:为系统中业务组件的多个业务方法添加某种通用功能(在执行目标方法之前、之后插入一些通用处理)
  • AOP 的过程:把业务方法中与业务无关的、却为业务模块所共同调用的操作抽离到不同的对象的方法中,最后使用代理的方式组合起来
  • 使用场景:日志、用户鉴权、全局性异常处理、性能监控、事务处理等
  • 术语:
    1. 切面(Aspect):用于组织多个 Advice,Advice 放在切面中定义,在实际应用中通常是一个存放通用功能实现的普通 Java 类,如日志切面、权限切面、事务切面等
    2. 通知/增强处理(Advice):AOP 框架在特定的切入点执行的增强处理,处理有 around、before 和 after 等类型,在实际应用中通常是切面类中的一个方法
    3. 连接点(Joinpoint):程序在运行过程中能够插入切面的点,如方法的调用、异常的抛出或成员变量的访问和更新等(Spring AOP 只支持将方法调用作为连接点)
    4. 切入点(Pointcut):可以插入增强处理的连接点,当某个连接点满足指定要求(由切入点的正则表达式来定义)时,该连接点将被添加增强处理,该连接点也就变成了切入点
    5. 织入(Weaving):将增强添加到目标对象(Target)中,并创建一个被增强的对象——AOP 代理(Proxy)的过程(Spring AOP 在运行时完成织入)

# AspectJ 切入点语法

  • Supported Pointcut Designators (opens new window)
  • Spring AOP 常用的切入点指示符(pointcut designators,PCD)
    • execution:用于匹配执行方法的连接点
      • execution(<修饰符>? <返回值类型> <所属类>?<方法名>(形参类型列表) <声明抛出的异常>?)? 表示该部分可省略
      • 通配符:
        • * 代表一个任意类型的参数
        • .. 代表零个或多个任意类型的参数,在表示类时,必须和 * 联合使用,而在表示入参时则单独使用
        • + 表示按类型匹配指定类的所有类,必须跟在类名后面,如 com.smart.Car+ 继承或扩展指定类的所有类,同时还包括指定类本身
      • execution(* com.example.app.service.impl.*.*(..)),匹配 com.example.app.service.impl 包中任意类的任意方法的执行
    • within:用于限定匹配特定域下的连接点。当使用 Spring AOP 的时候,只能匹配方法执行的连接点。
      • within(com.example.app.service..*),匹配在 com.example.app.service 包或其子包中的任意连接点
    • this:用于限定 AOP 代理对象必须是指定类型的实例,匹配该对象的所有连接点
      • this(com.example.app.service.AccountService),匹配实现了 com.example.app.service.AccountService 接口的 AOP 代理的所有连接点
    • target:用于限定目标对象必须是指定类型的实例,匹配该对象的所有连接点
      • target(com.example.app.service.AccountService),匹配实现了 com.example.app.service.AccountService 接口的目标对象的所有连接点
    • args:args(参数类型列表),用于对连接点的参数类型进行限制,要求参数是指定类型的实例
    • bean:bean(Bean的id或name),用于限定只匹配指定 Bean 实例内方法的连接点,支持使用 * 通配符,注意:bean 切入点表达式是 Spring AOP 额外支持的
    • @annotation:@annotation(注解类型),用于匹配标注有指定注解的方法
    • @within:用于匹配标注有指定注解的类内所有方法
      • @within(feign.Client+),切入 feign.Client 的实现类
    • @target:用于匹配标注有指定注解的类的目标对象内所有方法
    • @args:用于匹配入参标注有指定注解的方法
  • 注意:当使用 Spring AOP 的时候,只能匹配方法执行的连接点
  • Spring 支持使用如下三个逻辑运算符来组合切入点表达式
    • &&:要求连接点同时匹配两个切入点表达式
    • ||:只要连接点匹配任意一个切入点表达式
    • !:要求连接点不匹配指定的切入点表达式

AspectJ切点表达式函数

# AOP 的实现

  1. 静态 AOP 实现:在编译阶段对程序进行修改(编译时增强),以 AspectJ 为代表,需要使用特殊的编译器
    AspectJ 定义了如何表达、定义 AOP 编程中的语法规范,以及提供了编译、运行 AspectJ 的一些工具命令

  2. 动态 AOP 实现:在内存中以 JDK 动态代理或 cglib 动态地生成 AOP 代理类(运行时增强),以 Spring AOP 为代表

# Spring 的 AOP

  • 依赖的 jar 包:aopalliance.jar、aspectjweaver.jar
  • Spring 使用 AspectJ 方式来定义切入点和增强处理(没有使用 AspectJ 的编译器,底层使用的是动态代理技术)
  • 如果目标类有实现的接口,Spring 会使用 JDK 动态代理生成代理类,该代理类与目标类实现相同的接口(必须确保要拦截的目标方法在接口中有定义,方法只能使用 public 修饰)
  • 如果目标类没有实现接口,Spring 会使用 cglib 代理生成代理类,该代理类是目标类的子类(所以必须确保要拦截的目标方法可被子类访问,方法需使用 public 或 protected 修饰)
  • Spring 只能在方法级别上织入增强
  • 相关类:MethodInterceptor
  • Spring 只能切入由自己管理的 Bean
  • Spring Boot 2.x 默认使用 cglib 的方式生成代理类,spring.aop.proxy-target-class=true
  • ObjenesisCglibAopProxy#createProxyClassAndInstance,当通过 Objenesis 创建代理对象(此时无需调用类的构造函数)失败时,才通过默认构造函数创建代理对象。从 Spring 4 开始默认使用。
  • CglibAopProxy.CglibMethodInvocation#invokeJoinpoint,当调用的方法可以被代理时,就使用目标对象进行调用,否则使用代理对象进行调用

# 使用 XML 配置 AOP

<aop:config >

  • <aop:aspect >:配置切面,属性:id 该切面的标识名,ref 引用的切面 Bean,order 该切面 Bean 的优先级,子标签:<aop:pointcut >、<aop:before >、<aop:after >、<aop:after-retuming >、<aop:after-throwing >、<aop:around >,子标签属性:method、pointcut、pointcut-ref、throwing、returning
  • <aop:pointcut >:配置切入点,属性:id 定该切入点的标识名,expression 该切入点关联的切入点表达式
  • <aop:advisor >:将单独配置的增强处理切入点绑定在一起,属性:advice-ref、pointcut-ref、pointcut、order、id
<aop:config>
    <!-- what: 定义切面 -->
    <aop:aspect ref="txManager">
        <!-- where: 定义切入点 -->
        <aop:pointcut expression="execution(* com.example.tx.service.*Service.*(..))" id="pc"/>
        <!-- when: 定义在什么时机做增强处理,以及具体做什么增强处理 -->
        <aop:before method="begin" pointcut-ref="pc"/>  <!-- 前置增强 -->
        <aop:after-returning method="commit" pointcut-ref="pc"/>  <!-- 后置增强 -->
        <aop:after-throwing method="rollback" pointcut-ref="pc" throwing="ex"/>  <!-- 异常增强 -->
        <aop:after method="close" pointcut-ref="pc"/>  <!-- 最终增强 -->
        <aop:around method="allInOne" pointcut-ref="pc"/>  <!-- 环绕增强 -->
    </aop:aspect>
<aop:config>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 使用注解配置 AOP (opens new window)

  • 需在 XML 文件中开启 AOP 注解解析器 <aop:aspectj-autoproxy/>,启动 @AspectJ 支持;或者在启动类上添加 @EnableAspectJAutoProxy
  • 定义切面类 Bean @Aspect
  • 定义切入点 @Pointcut
    @Pointcut("execution(* com.example.tx.service.*Service.*(..))")
    使用一个返回值为 void、方法体为空的方法来命名切入点,public void pointCut() {}
  • 定义增强处理,指定切入点,即 value 属性值
    @Around("pointCut()")
    @Before("pointCut()")
    @After("pointCut()"):不管目标方法如何结束(包括成功完成和遇到异常中止两种情况)都会被织入
    @AfterReturning("pointCut()") :在目标方法正常完成后被织入
    @AfterThrowing(value="pointCut()", throwing="ex")
  • 当定义 Around 增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型(JoinPoint 类型的子类,代表了织入增强处理的连接点),在增强处理方法体内调用 ProceedingJoinPoint 参数的 proceed() 方法才会执行目标方法
  • JoinPoint 接口中常用的方法
    Object[] getArgs():返回执行目标方法时的参数
    Signature getSignature():返回被增强的方法的相关信息
    Object getTarget():返回被织入增强处理的目标对象
    Object getThis():返回 AOP 框架为目标对象生成的代理对象
  • ProceedingJoinPoint 接口(JoinPoint 的子接口)中常用的方法
    Object proceed():执行目标方法
    Object proceed(Object[] args):args 中的值被传入目标方法作为执行方法的实参
  • @DeclareParents:引入新的类来增强功能

切面 Bean 优先级越高(Order 值越小),越先执行入操作(越后执行出操作)

// 定义一个切面 Bean
@Aspect
@Component
@Slf4j
public class FourAdviceTest {
    @Pointcut("execution(* com.example.app.service.impl.*.*(..))")
    public void pointCut() }{}

    // @Around("execution(* com.example.app.service.impl.*.*(..))")
    @Around("pointCut()")
    public Object processTx(ProceedingJoinPoint jp) throws Throwable {
        log.info("[Around 增强]执行目标方法之前");
        // 访问执行目标方法的参数
        Object[] args = jp.getArgs();
        // 当执行目标方法的参数存在,且第一个参数是字符串时
        if (args != null && args.length > 0 && args[0].getClass() == String.class) {
            // 修改目标方法调用参数的第一个参数
            args[0] = "【增加的前缀】" + args[0];
        }
        // 执行目标方法,并保存目标方法执行后的返回值
        Object rvt = jp.proceed(args);
        log.info("[Around 增强]执行目标方法之后");
        // 如果 rvt 的类型是 Integer,将 rvt 改为它的平方
        if (rvt instanceof Integer)
            rvt = (Integer) rvt * (Integer) rvt;
        return rvt;
    }

    @Before("pointCut()")
    public void authority(JoinPoint jp) {
        log.info("[Before 增强]");
    }

    @After("pointCut()")
    public void release(JoinPoint jp) {
        log.info("[After 增强]");
    }

    @AfterReturning(pointcut = "pointCut()", returning = "rvt")
    public void log(JoinPoint jp, Object rvt) {
        log.info("[AfterReturning 增强]");
    }

    @AfterThrowing(pointcut = "pointCut()", throwing="ex")
    // 声明 ex 时指定的类型会限制目标方法必须抛出指定类型的异常
    // 此处将 ex 的类型声明为 Throwable,意味着对目标方法抛出的异常不加限制
    public void recovery(Throwable ex) {
        log.info("[AfterThrowing 增强]");
    }
}

// 输出
[Around 增强]执行目标方法之前
[Before 增强]
[Around 增强]执行目标方法之后
[After 增强]
[AfterReturning 增强][AfterThrowing 增强]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# Spring 的 JDBC

# JdbcTemplate 类

  • 构造器:JdbcTemplate(DataSource dataSource)(调用该构造器时,dataSource 不能为 null,否则抛出异常)

  • 实例方法:query、queryForObject、queryForList、update、batchUpdate、execute int update(String sql, Object... args)
    T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
    List<T> query(String sql, RowMapper<T> rowMapper, Object... args)

  • SimpleJdbcInsert 类、NamedParameterJdbcTemplate 类

通过实现 RowMapper 接口完成 JdbcTemplate 的映射关系

# JdbcDaoSupport 抽象类

  • 实例方法: void setDataSource(DataSource dataSource)
    JdbcTemplate getJdbcTemplate()

# JDBC 异常抽象

  • Spring JDBC 会将数据操作的异常转换为 DataAccessException
  • 通过 SQLErrorCodeSQLEXceptionTranslator 解析错误码
  • ErrorCode 定义:
    1. org/springframework/jdbc/support/sql-error-codes.xml
    2. classpath 下的 sql-error-codes.xml(可定制错误码)

# Spring 的事务管理

# 相关接口/类

  • TransactionDefinition,定义了一个事务规则:事务隔离、事务传播、事务超时、只读状态

    • 事务隔离级别:默认值为 ISOLATION_DEFAULT(-1),使用数据库的默认隔离级别
    • 事务的传播方式:当一个事务方法调用另外一个方法时,应该怎么处理自身的事务。枚举类 Propagation 中定义了 7 种传播规则:
      1. REQUIRED:需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法
      2. SUPPORTS:支持事务,如果当前存在事务,就沿用当前事务,如果不存在,则继续采用无事务的方式运行子方法
      3. MANDATORY:必须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务,就沿用当前事务
      4. REQUIRES_NEW:无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
      5. NOT_SUPPORTED:不支持事务,当前存在事务时,将挂起事务,运行方法
      6. NEVER:不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
      7. NESTED:在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的 SQL,而不回滚当前方法的事务(需要在当前方法调用子方法时捕获子方法抛出的异常
        • 嵌套事务,将创建一个依赖于外层事务的子事务,子事务执行成功后是否能提交由外层事务控制,但它无法影响外层事务。即子事务失败,而外层事务成功时,子事务回滚,而外层事务提交;子事务成功,而外层事务失败时,子事务、外层事务都回滚。
        • 在大部分的数据库中,一段 SQL 语句中可以设置一个标志位,然后后面的代码执行时如果有异常,只是回滚到这个标志位的数据状态,而不会让这个标志位之前的代码也回滚。这个标志位,在数据库的概念中被称为保存点(save point)。
        • Spring 使用保存点技术来完成让子事务回滚而不致使当前事务回滚。当数据库支持保存点技术时,就启用保存点技术;如果不能支持,就新建一个事务去运行代码,即等价于 REQUIRES_NEW 传播行为。
    • 事务超时时间
    • 是否只读
  • PlatformTransactionManager,Spring 具体的事务管理由 PlatformTransactionManager 的不同实现类来完成,常用实现类:DataSourceTransactionManager,接口方法:

    • TransactionStatus getTransaction(@Nullable TransactionDefinition definition):开始事务,transactionDefinition 可从容器中获取
    • void commit(TransactionStatus status):提交事务
    • void rollback(TransactionStatus status):回滚事务
  • TransactionStatus,表示一个事务

  • ProxyTransactionManagementConfiguration、TransactionInterceptor

  • TransactionAspectSupport#invokeWithinTransaction

  • @TransactionalEventListener:根据 TransactionPhase 调用的 EventListener

  • TransactionSynchronizationManager,事务同步管理器,管理每个线程的资源和事务同步,包括资源绑定、激活事务同步等。类方法:

    • Object getResource(Object key)
    • void bindResource(Object key, Object value)
    • Object unbindResource(Object key)
    • boolean isSynchronizationActive():判断当前事务是否为活跃,对于被挂起的线程返回 true
    • boolean isActualTransactionActive():判断当前事务是否为实际活跃的事务,对于被挂起的线程返回 false
    • void registerSynchronization(TransactionSynchronization synchronization):注册事务同步
    // org.springframework.cache.transaction.TransactionAwareCacheDecorator#put
    public void put(final Object key, @Nullable final Object value) {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    this.targetCache.put(key, value);
                }
            });
        }
        else {
            this.targetCache.put(key, value);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

# 配置事务管理器

<!-- 配置 JDBC 数据源的事务管理器,使用 DataSourceTransactionManager 实现类 -->
<!-- 配置 DataSourceTransactionManager 时需要依赖注入 DataSource 的引用 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
1
2
3
4
5

# 声明式事务

# 使用 XML 配置事务

  • <tx:method > 子标签的常用属性
    • name:指定对哪些方法起作用
    • propagation:事务的传播方式,属性值:REQUIRED(默认值,要求在事务环境中执行该方法,如果当前执行线程已处于事务环境中,则直接调用,如果当前执行线程不处于事务环境中,则启动新的事务)、SUPPORTS(如果当前执行线程处于事务环境中,则使用当前事务,否则不使用事务)
    • read-only:是否是只读事务,属性值:false(默认值)、true
    • rollback-for:触发事务回滚的 Exception,默认是所有 Runtime 异常回滚
    • no-rollback-for:不触发事务回滚的 Exception,默认是所有 checked 异常不回滚
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事务增强处理 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="list*" read-only="true"/>
        <tx:method name="select*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!-- 配置 AOP -->
<aop:config>
    <!-- 定义一个切入点,通过 expression 指定对应的切入点表达式 -->
    <aop:pointcut expression="execution(* com.example.ssm.service.*Servcie.*(..))" id="pc"/>
    <!-- 将增强处理和切入点绑定在一起:指定在 pc 切入点应用 txAdvice 事务增强处理 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 使用注解配置事务

  • 开启事务注解解析器,并指定使用的事务管理器 Bean 的 id(默认为 transactionManager):<tx:annotation-driven transaction-manager="txManager"/>
  • @Transactional 建议标注在实现类(或实现类的方法)上,而不是所实现的接口
  • @Transactional 可用属性
    • transactionManager:指定使用的事务管理器 Bean 的 id
    • isolation:用于指定事务的隔离级别,默认为底层事务的隔离级别 Isolation.DEFAULT
    • propagation:指定事务传播行为,默认 Propagation.REQUIRED
    • readOnly:指定事务是否只读,默认 false
    • rollbackFor:指定遇到特定异常时强制回滚事务,默认 ex instanceof RuntimeException || ex instanceof ErrorRuntimeException 或 Error)时才回滚
    • rollbackForClassName:指定遇到特定的多个异常时强制回滚事务
    • noRollbackFor:指定遇到特定异常时强制不回滚事务
    • noRollbackForClassName:指定遇到特定的多个异常时强制不回滚事务
    • timeout:指定事务的超时时长
  • TransactionAspectSupport#invokeWithinTransaction
  • TransactionAspectSupport#completeTransactionAfterThrowing
  • RuleBasedTransactionAttribute#rollbackOn(获胜规则是最浅的规则,即继承层次结构中最接近异常的规则)

# 编程式事务

  • 方式一:获取容器中的 transactionManager(PlatformTransactionManager 的实例),通过该接口提供的方法来开始事务、提交事务和回滚事务
  • 方式二:使用 TransactionTemplate 类来进行事务操作 ,T execute(TransactionCallback<T> action) 重写 TransactionCallback、TransactionCallbackWithoutResult 中的 doInTransaction 或 doInTransactionWithoutResult 方法

# 注意事项

  1. 自调用失效问题
    • 一个类自身方法之间的调用,称为自调用
    • 在自调用过程中,是类自身 this 的调用,而不是代理对象去调用,不会产生 AOP
    • 解决方法:访问增强后的代理类的方法,而非直接访问自身的方法,如:
      1. 用一个 service 去调用另一个 service
      2. 从 Spring IoC 容器中获取 service 代理对象去启用 AOP
      3. 如果其代理通过使用 AbstractAutoProxyCreator 子类创建(如类标注有 @Transactional 或者 @Caching 等注解,只标注 @Async 的类除外),可以在被代理方法内使用 AopContext.currentProxy(); 获取当前代理对象,但需先设置 ProxyConfig.exposeProxy 为 true,默认 false
      4. 使用 AspectJ 进行增强 mode=AdviceMode.ASPECTJ
  2. 只有 public 方法事务有效:AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
  3. 部分事务失败全局回滚
    • 默认情况下,发生异常后当前的事务被标记为 rollback-only,外层事务管理器再 commit 时就会抛出 UnexpectedRollbackException(Transaction rolled back because it has been marked as rollback-only)(可以在 catch 块里加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 用于显式控制回滚)
    • AbstractPlatformTransactionManager.globalRollbackOnParticipationFailure,设置在参与事务失败后是否将现有事务全局标记为 rollback-only
      • true(默认):如果参与事务(事务传播规则为 PROPAGATION_REQUIRED 或 PROPAGATION_SUPPORTS)失败,则该事务将全局标记为 rollback-only,这种事务唯一可能的结果是回滚,因此事务发起者不能再提交事务
      • false:表示让事务发起者决定是否回滚,如果参与的事务因异常而失败,调用者可以处理异常并决定回滚,独立于子事务的回滚规则
    • 在嵌套事务中处理子事务失败,推荐使用 PROPAGATION_NESTED
    • 见 AbstractPlatformTransactionManager#setGlobalRollbackOnParticipationFailure 注释
  4. 避免长事务:对事务方法进行拆分,尽量让事务变小,变快,减小事务的颗粒度;使用编程式事务手动控制事务范围

注意确认调用 @Transactional 注解标记的方法是 public 且非静态的,并且是通过 Spring 注入的 Bean 进行调用的

手动设置让当前事务处于回滚状态 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

# Spring 的缓存抽象

  • Spring 可以支持多种缓存管理机制,如 ConcurrentMap、EhCache、Redis、Caffeine、JCache 等,并提供了缓存处理器的接口 CacheManager 和与之相关的类

# 工具类

# StringUtils

  • boolean isEmpty(Object str):字符串是否为空或者空字符串
  • boolean hasLength(CharSequence str):字符串是否为空,或者长度为 0
  • boolean hasText(String str):字符串是否有内容(不为空,且不全为空格)
  • boolean containsWhitespace(String str):字符串是否包含空格
  • String trimWhitespace(String str):去掉字符串前后的空格
  • String trimAllWhitespace(String str):去掉字符串中所有的空格
  • String unqualify(String qualifiedName):得到以 . 分割的最后一个值(可以用来获取类名或者文件后缀)
  • String unqualify(String qualifiedName, char separator):得到以给定字符分割的最后一个值(可以用来获取文件名 File.separatorChar
  • String getFilename(String path):获取文件名
  • String getFilenameExtension(String path):获取文件后缀名
  • String capitalize(String str):首字母大写
  • String uncapitalize(String str):取消首字母大写
  • boolean substringMatch(CharSequence str, int index, CharSequence substring):判断从指定索引开始,是否匹配子字符串
  • int countOccurrencesOf(String str, String sub):判断子字符串在字符串中出现的次数
  • String replace(String inString, String oldPattern, String newPattern):在字符串中使用子字符串替换
  • String delete(String inString, String pattern):删除所有匹配的子字符串
  • String deleteAny(String inString, String charsToDelete):删除子字符串中任意出现的字符
  • String quote(String str):在字符串前后增加单引号,比较适合在日志时候使用
  • String[] addStringToArray(String[] array, String str):把一个字符串添加到一个字符串数组中
  • String[] concatenateStringArrays(String[] array1, String[]array2):连接两个字符串数组
  • String[] mergeStringArrays(String[] array1, String[] array2):连接两个字符串数组,去掉重复元素
  • String[] sortStringArray(String[] array):字符串数组排序
  • String[] tokenizeToStringArray(String str, String delimiters):对每一个元素执行 trim 操作,并去掉空字符串
  • String[] delimitedListToStringArray(String str, String delimiter):分割字符串,以 delimiter 作为整体分隔符
  • Set<String> commaDelimitedListToSet(String str):使用逗号分割字符串,并放到 set 中去重
  • String collectionToDelimitedString(Collection<?> coll, String delim, String prefix, String suffix):将集合中的每个元素使用前缀、后缀、分隔符连接
  • String collectionToDelimitedString(Collection<?> coll, String delim):将集合中的每个元素使用指定字符串连接
  • String arrayToDelimitedString(Object[] arr, String delim):数组使用指定字符串连接
  • String[] split(String toSplit, String delimiter):在第一次出现分隔符时分割
  • Properties splitArrayElementsIntoProperties(String[] array, String delimiter):把字符串数组中的每一个字符串按照给定的分隔符装配到一个 Properties 中

# StopWatch

  • 秒表,可用于查看多个任务的耗时情况,线程不安全
  • 构造器:StopWatch(String id)
  • void start(String taskName)
  • void stop()
  • long getTotalTimeMillis()long getLastTaskTimeMillis()
  • TaskInfo[] getTaskInfo()TaskInfo getLastTaskInfo()
  • String shortSummary()

# NumberUtils

  • T convertNumberToTargetClass(Number number, Class<T> targetClass)
  • T parseNumber(String text, Class<T> targetClass)
  • T parseNumber(String text, Class<T> targetClass, NumberFormat numberFormat)

# CollectionUtils

# ObjectUtils

  • boolean isCheckedException(Throwable ex)
  • boolean isArray(Object obj)
  • boolean isEmpty(Object[] array)
  • boolean isEmpty(Object obj)
  • Object unwrapOptional(Object obj)
  • boolean containsElement(Object[] array, Object element)
  • A[] addObjectToArray(A[] array, O obj)
  • boolean nullSafeEquals(Object o1, Object o2)
  • int nullSafeHashCode(Object obj)
  • String nullSafeToString(Object obj)
  • String nullSafeToString(Object[] array)

# FileCopyUtils

  • 用于文件和流复制,所有复制方法都使用 4096 字节的块大小,并在完成后关闭所有受影响的流
  • int copy(File in, File out)
  • void copy(byte[] in, File out)
  • int copy(InputStream in, OutputStream out)
  • void copy(byte[] in, OutputStream out)
  • int copy(Reader in, Writer out)
  • void copy(String in, Writer out)
  • String copyToString(Reader in)
  • byte[] copyToByteArray(File in)
  • byte[] copyToByteArray(InputStream in)

# StreamUtils

  • 用于处理流的简单实用方法,此类的复制方法与 FileCopyUtils 中定义的复制方法类似,只是在完成后所有受影响的流都保持打开状态

# FileSystemUtils

  • boolean deleteRecursively(File root):递归删除
  • boolean deleteRecursively(Path root)
  • void copyRecursively(File src, File dest):递归复制
  • void copyRecursively(Path src, Path dest)

# ResourceUtils

  • 将给定的资源位置解析为文件系统中的文件
  • 支持的资源位置前缀:file:
  • File getFile(String resourceLocation):将给定的资源位置解析为文件系统中的文件,不检查文件是否实际存在
  • File getFile(URL resourceUrl)
  • File getFile(URI resourceUri)

jar 包中的文件只能通过类加载器文件流的方式读取
InputStream inputStream = new ClassPathResource("applicationContext.xml").getInputStream();

# PropertiesLoaderUtils

  • 主要用于加载 Properties 文件

  • Properties loadProperties(Resource resource):从一个资源文件加载 Properties

  • Properties loadProperties(EncodedResource resource):加载资源文件,传入的是提供了编码的资源类(EncodedResource)

  • void fillProperties(Properties props, Resource resource):从一个资源类中加载资源,并填充到指定的 Properties 对象中

  • void fillProperties(Properties props, EncodedResource resource):从一个编码资源类中加载资源,并填充到指定的 Properties 对象中

  • Properties loadAllProperties(String resourceName):根据资源文件名称,加载并合并 classpath 中的所有资源文件

  • Properties loadAllProperties(String resourceName, ClassLoader classLoader):从指定的 ClassLoader 中,根据资源文件名称,加载并合并 classpath 中的所有资源文件

# BeanUtils

  • void copyProperties(Object source, Object target, String... ignoreProperties):浅克隆(原理:反射)
  • boolean isSimpleProperty(Class<?> clazz):判断给定的类型是否表示简单属性:八大基本类型/包装类型、字符串或其它 CharSequence、Number、Enum、Date、URI、URL、Locale、Class 或对应的数组
  • T instantiateClass(Class<T> clazz):使用无参构造器实例化
  • Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>... paramTypes)

# BeanCopier

  • org.springframework.cglib.beans.BeanCopier
  • 原理:修改字节码
  • 类方法:BeanCopier create(Class source, Class target, boolean useConverter):通过操作字节码生成用于两个 JavaBean 间进行复制的类
  • 抽象方法:void copy(Object var1, Object var2, Converter var3):对两个 Bean 间属性名和类型完全相同的变量进行拷贝

# BeanMap

  • org.springframework.cglib.beans.BeanMap implements Map
  • 类方法:BeanMap create(Object bean):JavaBean 对象转 Map 对象,BeanMap.create(bean).keySet();
  • 实例方法:void putAll(Map t):Map 对象转 JavaBean 对象,BeanMap.create(bean).putAll(map);

# AopUtils

  • org.springframework.aop.support.AopUtils

  • boolean isAopProxy(Object object):是否是代理对象

  • boolean isJdkDynamicProxy(Object object):判断是否是 JDK 代理对象

  • boolean isCglibProxy(Object object):判断是否是 cglib 代理对象

  • Class<?> getTargetClass(Object candidate):获取对象的真实类型

  • Method getMostSpecificMethod(Method method, Class<?> targetClass):获取真实对象上对应的方法

  • Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args):在 target 对象上,使用 args 参数列表执行 method

# ClassUtils

# AnnotationUtils

# AnnotatedElementUtils

  • A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType):找到指定的注解类型的第一个注释(包括父类和接口、父类方法和接口方法上的注解)

# ReflectionUtils

  • Field findField(Class<?> clazz, String name)
  • Field findField(Class<?> clazz, String name, Class<?> type)
  • Object getField(Field field, Object target)
  • void setField(Field field, Object target, Object value)
  • Method findMethod(Class<?> clazz, String name)
  • Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes)
  • Object invokeMethod(Method method, Object target)
  • Object invokeMethod(Method method, Object target, Object... args)

# UUID 生成器

  • new AlternativeJdkIdGenerator().generateId()

# Assert 断言工具类

  • 如果参数值被视为无效,则将抛出 IllegalArgumentException (通常)

  • Assert.notNull(Object object, "object is required"):对象非空

  • Assert.isTrue(Object object, "object must be true"):对象必须为 true

  • Assert.notEmpty(Collection collection, "collection must not be empty"):集合非空

  • Assert.hasLength(String text, "text must be specified"):字符不为 null 且字符长度不为 0

  • Assert.hasText(String text, "text must not be empty"):text 不为 null 且必须至少包含一个非空格的字符

  • Assert.isInstanceOf(Class clazz, Object obj, "clazz must be of type [clazz]"):obj 必须能被正确造型成为 clazz 指定的类

# AntPathMatcher

  • 构造器:AntPathMatcher()(默认的路径分隔符 "/")、AntPathMatcher(String pathSeparator)
  • 常用实例方法:
    • boolean isPattern(String path):判断传入的 path 是否可以作为 pattern 使用,即判断 path 是否包含 '*' 或 '?'
    • boolean match(String pattern, String path)使用 pattern 匹配 path
    • String extractPathWithinPattern(String pattern, String path):提取 path 中匹配到的部分
    • Map<String,String> extractUriTemplateVariables(String pattern, String path):提取 URI 模板变量,如 pattern(/hotels/{id:[0-9]+}), path(/hotels/1),解析出 {id=1}
    • String combine(String pattern1, String pattern2):合并 pattern,即 pattern1 然后 pattern2

# Base64Utils

  • byte[] encode(byte[] src):Base64 编码
  • byte[] decode(byte[] src):Base64 解码
  • String encodeToString(byte[] src)
  • byte[] decodeFromString(String src)
  • byte[] encodeUrlSafe(byte[] src):Base64 编码(using the RFC 4648 "URL and Filename Safe Alphabet")
  • static byte[] decodeUrlSafe(byte[] src):Base64 解码(using the RFC 4648 "URL and Filename Safe Alphabet")
  • String encodeToUrlSafeString(byte[] src)
  • byte[] decodeFromUrlSafeString(String src)

# DigestUtils

  • byte[] md5Digest(byte[] bytes):计算 MD5 摘要
  • byte[] md5Digest(InputStream inputStream)
  • String md5DigestAsHex(byte[] bytes):返回十六进制的 MD5 摘要字符串
  • String md5DigestAsHex(InputStream inputStream)

# MultiValueMap

  • MultiValueMap<K, V> extends Map<K, List<V>>,一个 Key 对应多个 Value
  • 常用实现类:LinkedMultiValueMap、HttpHeaders
  • V getFirst(K key)
  • void add(K key, V value)
  • void addAll(K key, List<? extends V> values)
  • void addAll(MultiValueMap<K, V> values)
  • void set(K key, V value)
  • void setAll(Map<K, V> values)
  • Map<K, V> toSingleValueMap():Return a Map with the first values contained in this MultiValueMap

# ConcurrentReferenceHashMap

# SerializationUtils

  • byte[] serialize(Object object)
  • Object deserialize(byte[] bytes)

# NestedExceptionUtils

  • Throwable getMostSpecificCause(Throwable original):获取异常最内部的原因(根本原因)或异常本身

# PropertyPlaceholderHelper

  • 属性占位符替换
  • 构造器:
    • PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix)
    • PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, boolean ignoreUnresolvablePlaceholders)
  • 实例方法:
    • String replacePlaceholders(String value, Properties properties)
    • String replacePlaceholders(String value, PlaceholderResolver placeholderResolver)

# ParameterNameDiscoverer

  • 获取非抽象方法的参数名称
  • 常用实现类 DefaultParameterNameDiscoverer
ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
parameterNameDiscoverer.getParameterNames(method);
1
2
Updated at: 2024-04-22 15:04:27